import Foundation
import UIKit
import ComponentFlow
import AccountContext
import AVKit
import MultilineTextComponent
import Display
import ShimmerEffect
import TelegramCore
import SwiftSignalKit
import AvatarNode
import Postbox
import TelegramVoip
import ComponentDisplayAdapters

final class MediaStreamVideoComponent: Component {
    let call: PresentationGroupCallImpl
    let hasVideo: Bool
    let isVisible: Bool
    let isAdmin: Bool
    let peerTitle: String
    let activatePictureInPicture: ActionSlot<Action<Void>>
    let deactivatePictureInPicture: ActionSlot<Void>
    let bringBackControllerForPictureInPictureDeactivation: (@escaping () -> Void) -> Void
    let pictureInPictureClosed: () -> Void
    let isFullscreen: Bool
    let onVideoSizeRetrieved: (CGSize) -> Void
    let videoLoading: Bool
    let callPeer: Peer?
    let onVideoPlaybackLiveChange: (Bool) -> Void
    
    init(
        call: PresentationGroupCallImpl,
        hasVideo: Bool,
        isVisible: Bool,
        isAdmin: Bool,
        peerTitle: String,
        isFullscreen: Bool,
        videoLoading: Bool,
        callPeer: Peer?,
        activatePictureInPicture: ActionSlot<Action<Void>>,
        deactivatePictureInPicture: ActionSlot<Void>,
        bringBackControllerForPictureInPictureDeactivation: @escaping (@escaping () -> Void) -> Void,
        pictureInPictureClosed: @escaping () -> Void,
        onVideoSizeRetrieved: @escaping (CGSize) -> Void,
        onVideoPlaybackLiveChange: @escaping (Bool) -> Void
    ) {
        self.call = call
        self.hasVideo = hasVideo
        self.isVisible = isVisible
        self.isAdmin = isAdmin
        self.peerTitle = peerTitle
        self.videoLoading = videoLoading
        self.activatePictureInPicture = activatePictureInPicture
        self.deactivatePictureInPicture = deactivatePictureInPicture
        self.bringBackControllerForPictureInPictureDeactivation = bringBackControllerForPictureInPictureDeactivation
        self.pictureInPictureClosed = pictureInPictureClosed
        self.onVideoPlaybackLiveChange = onVideoPlaybackLiveChange
        
        self.callPeer = callPeer
        self.isFullscreen = isFullscreen
        self.onVideoSizeRetrieved = onVideoSizeRetrieved
    }
    
    public static func ==(lhs: MediaStreamVideoComponent, rhs: MediaStreamVideoComponent) -> Bool {
        if lhs.call !== rhs.call {
            return false
        }
        if lhs.hasVideo != rhs.hasVideo {
            return false
        }
        if lhs.isVisible != rhs.isVisible {
            return false
        }
        if lhs.isAdmin != rhs.isAdmin {
            return false
        }
        if lhs.peerTitle != rhs.peerTitle {
            return false
        }
        if lhs.isFullscreen != rhs.isFullscreen {
            return false
        }
        if lhs.videoLoading != rhs.videoLoading {
            return false
        }
        return true
    }
    
    public final class State: ComponentState {
        override init() {
            super.init()
        }
    }
    
    public func makeState() -> State {
        return State()
    }
    
    public final class View: UIView, AVPictureInPictureControllerDelegate, ComponentTaggedView {
        public final class Tag {
        }
        
        private let videoRenderingContext = VideoRenderingContext()
        private let blurTintView: UIView
        private var videoBlurView: VideoRenderingView?
        private var videoView: VideoRenderingView?
        
        private var videoPlaceholderView: UIView?
        private var noSignalView: ComponentHostView<Empty>?
        private let loadingBlurView = CustomIntensityVisualEffectView(effect: UIBlurEffect(style: .light), intensity: 0.4)
        private let shimmerOverlayView = CALayer()
        private var pictureInPictureController: AVPictureInPictureController?
        
        private var component: MediaStreamVideoComponent?
        private var hadVideo: Bool = false
        
        private var requestedExpansion: Bool = false
        
        private var noSignalTimer: Foundation.Timer?
        private var noSignalTimeout: Bool = false
        
        private let videoBlurGradientMask = CAGradientLayer()
        private let videoBlurSolidMask = CALayer()
        
        private var wasVisible = true
        private var borderShimmer = StandaloneShimmerEffect()
        private let shimmerBorderLayer = CALayer()
        private let placeholderView = UIImageView()
        
        private var videoStalled = false {
            didSet {
                if videoStalled != oldValue {
                    self.updateVideoStalled(isStalled: self.videoStalled, transition: nil)
//                    state?.updated()
                }
            }
        }
        var onVideoPlaybackChange: ((Bool) -> Void) = { _ in }
        
        private var frameInputDisposable: Disposable?
        
        private var stallTimer: Foundation.Timer?
        private let fullScreenBackgroundPlaceholder = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
        
        private var avatarDisposable: Disposable?
        private var didBeginLoadingAvatar = false
        private var timeLastFrameReceived: CFAbsoluteTime?
        
        private var isFullscreen: Bool = false
        private let videoLoadingThrottler = Throttler<Bool>(duration: 1, queue: .main)
        private var wasFullscreen: Bool = false
        private var isAnimating = false
        private var didRequestBringBack = false
        
        private weak var state: State?
        
        private var lastPresentation: UIView?
        private var pipTrackDisplayLink: CADisplayLink?
        
        private var livestreamVideoView: LivestreamVideoViewV1?
        
        override init(frame: CGRect) {
            self.blurTintView = UIView()
            self.blurTintView.backgroundColor = UIColor(white: 0.0, alpha: 0.55)
            super.init(frame: frame)
            
            self.isUserInteractionEnabled = false
            self.clipsToBounds = true
            
            self.addSubview(self.blurTintView)
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        deinit {
            avatarDisposable?.dispose()
            frameInputDisposable?.dispose()
            self.pipTrackDisplayLink?.invalidate()
            self.pipTrackDisplayLink = nil
        }
        
        public func matches(tag: Any) -> Bool {
            if let _ = tag as? Tag {
                return true
            }
            return false
        }
        
        func expandFromPictureInPicture() {
            if let pictureInPictureController = self.pictureInPictureController, pictureInPictureController.isPictureInPictureActive {
                self.requestedExpansion = true
                self.pictureInPictureController?.stopPictureInPicture()
            }
        }
        
        private func updateVideoStalled(isStalled: Bool, transition: ComponentTransition?) {
            if isStalled {
                guard let component = self.component else { return }
                
                if let peerId = component.call.peerId, let frameView = lastFrame[peerId.id.description] {
                    frameView.removeFromSuperview()
                    placeholderView.subviews.forEach { $0.removeFromSuperview() }
                    placeholderView.addSubview(frameView)
                    frameView.frame = placeholderView.bounds
                }
                
                if !hadVideo && placeholderView.superview == nil {
                    addSubview(placeholderView)
                }
                
                let needsFadeInAnimation = hadVideo
                
                if loadingBlurView.superview == nil {
                    //addSubview(loadingBlurView)
                    if needsFadeInAnimation {
                        let anim = CABasicAnimation(keyPath: "opacity")
                        anim.duration = 0.5
                        anim.fromValue = 0
                        anim.toValue = 1
                        loadingBlurView.layer.opacity = 1
                        anim.fillMode = .forwards
                        anim.isRemovedOnCompletion = false
                        loadingBlurView.layer.add(anim, forKey: "opacity")
                    }
                }
                loadingBlurView.layer.zPosition = 998
                self.noSignalView?.layer.zPosition = loadingBlurView.layer.zPosition + 1
                if shimmerBorderLayer.superlayer == nil {
                    loadingBlurView.contentView.layer.addSublayer(shimmerBorderLayer)
                }
                loadingBlurView.clipsToBounds = true
                
                let cornerRadius = loadingBlurView.layer.cornerRadius
                shimmerBorderLayer.cornerRadius = cornerRadius
                shimmerBorderLayer.masksToBounds = true
                shimmerBorderLayer.compositingFilter = "softLightBlendMode"
                
                let borderMask = CAShapeLayer()
                
                shimmerBorderLayer.mask = borderMask
                
                if let transition, shimmerBorderLayer.mask != nil {
                    let initialPath = CGPath(roundedRect: .init(x: 0, y: 0, width: shimmerBorderLayer.bounds.width, height: shimmerBorderLayer.bounds.height), cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)
                    borderMask.path = initialPath
                    transition.setFrame(layer: shimmerBorderLayer, frame: loadingBlurView.bounds)
                    
                    let borderMaskPath = CGPath(roundedRect: .init(x: 0, y: 0, width: shimmerBorderLayer.bounds.width, height: shimmerBorderLayer.bounds.height), cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)
                    transition.setShapeLayerPath(layer: borderMask, path: borderMaskPath)
                } else {
                    shimmerBorderLayer.frame = loadingBlurView.bounds
                    let borderMaskPath = CGPath(roundedRect: .init(x: 0, y: 0, width: shimmerBorderLayer.bounds.width, height: shimmerBorderLayer.bounds.height), cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)
                    borderMask.path = borderMaskPath
                }
                
                borderMask.fillColor = UIColor.white.withAlphaComponent(0.4).cgColor
                borderMask.strokeColor = UIColor.white.withAlphaComponent(0.7).cgColor
                borderMask.lineWidth = 3
                borderMask.compositingFilter = "softLightBlendMode"
                
                borderShimmer = StandaloneShimmerEffect()
                borderShimmer.layer = shimmerBorderLayer
                borderShimmer.updateHorizontal(background: .clear, foreground: .white)
                loadingBlurView.alpha = 1
            } else {
                if hadVideo && !isAnimating && loadingBlurView.layer.opacity == 1 {
                    let anim = CABasicAnimation(keyPath: "opacity")
                    anim.duration = 0.4
                    anim.fromValue = 1.0
                    anim.toValue = 0.0
                    self.loadingBlurView.layer.opacity = 0
                    anim.fillMode = .forwards
                    anim.isRemovedOnCompletion = false
                    isAnimating = true
                    anim.completion = { [weak self] _ in
                        guard self?.videoStalled == false else { return }
                        self?.loadingBlurView.removeFromSuperview()
                        self?.placeholderView.removeFromSuperview()
                        self?.isAnimating = false
                    }
                    loadingBlurView.layer.add(anim, forKey: "opacity")
                }
            }
        }
        
        func update(component: MediaStreamVideoComponent, availableSize: CGSize, state: State, transition: ComponentTransition) -> CGSize {
            self.state = state
            self.component = component
            self.onVideoPlaybackChange = component.onVideoPlaybackLiveChange
            self.isFullscreen = component.isFullscreen
            
            if let peer = component.callPeer, !didBeginLoadingAvatar {
                didBeginLoadingAvatar = true
                
                avatarDisposable = peerAvatarCompleteImage(account: component.call.account, peer: EnginePeer(peer), size: CGSize(width: 250.0, height: 250.0), round: false, font: Font.regular(16.0), drawLetters: false, fullSize: false, blurred: true).start(next: { [weak self] image in
                    DispatchQueue.main.async {
                        self?.placeholderView.contentMode = .scaleAspectFill
                        self?.placeholderView.image = image
                    }
                })
            }
            
            if !component.hasVideo || component.videoLoading || self.videoStalled {
                updateVideoStalled(isStalled: true, transition: transition)
            } else {
                updateVideoStalled(isStalled: false, transition: transition)
            }
            
            if component.hasVideo, self.videoView == nil {
                if let input = component.call.video(endpointId: "unified") {
                    var _stallTimer: Foundation.Timer { Foundation.Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
                        guard let strongSelf = self else { return timer.invalidate() }
                        
                        let currentTime = CFAbsoluteTimeGetCurrent()
                        if let lastFrameTime = strongSelf.timeLastFrameReceived,
                           currentTime - lastFrameTime > 0.5 {
                            strongSelf.videoLoadingThrottler.publish(true, includingLatest: true) { isStalled in
                                strongSelf.videoStalled = isStalled
                                strongSelf.onVideoPlaybackChange(!isStalled)
                            }
                        }
                    } }
                    
                    // TODO: use mapToThrottled (?)
                    frameInputDisposable = input.start(next: { [weak self] input in
                        guard let strongSelf = self else { return }
                        
                        strongSelf.timeLastFrameReceived = CFAbsoluteTimeGetCurrent()
                        strongSelf.videoLoadingThrottler.publish(false, includingLatest: true) { isStalled in
                            strongSelf.videoStalled = isStalled
                            strongSelf.onVideoPlaybackChange(!isStalled)
                        }
                    })
                    stallTimer = _stallTimer
                    self.clipsToBounds = component.isFullscreen // or just true
                    
                    if let videoView = self.videoRenderingContext.makeView(input: input, blur: false, forceSampleBufferDisplayLayer: true) {
                        self.videoView = videoView
                        self.addSubview(videoView)
                        videoView.alpha = 0
                        UIView.animate(withDuration: 0.3) {
                            videoView.alpha = 1
                        }
                        if let sampleBufferVideoView = videoView as? SampleBufferVideoRenderingView {
                            sampleBufferVideoView.sampleBufferLayer.masksToBounds = true
                            
                            if #available(iOS 13.0, *) {
                                sampleBufferVideoView.sampleBufferLayer.preventsDisplaySleepDuringVideoPlayback = true
                            }
                            
                            final class PlaybackDelegateImpl: NSObject, AVPictureInPictureSampleBufferPlaybackDelegate {
                                var onTransitionFinished: (() -> Void)?
                                func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, setPlaying playing: Bool) {
                                    
                                }
                                
                                func pictureInPictureControllerTimeRangeForPlayback(_ pictureInPictureController: AVPictureInPictureController) -> CMTimeRange {
                                    return CMTimeRange(start: .zero, duration: .positiveInfinity)
                                }
                                
                                func pictureInPictureControllerIsPlaybackPaused(_ pictureInPictureController: AVPictureInPictureController) -> Bool {
                                    return false
                                }
                                
                                func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, didTransitionToRenderSize newRenderSize: CMVideoDimensions) {
                                    onTransitionFinished?()
                                }
                                
                                func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, skipByInterval skipInterval: CMTime, completion completionHandler: @escaping () -> Void) {
                                    completionHandler()
                                }
                                
                                public func pictureInPictureControllerShouldProhibitBackgroundAudioPlayback(_ pictureInPictureController: AVPictureInPictureController) -> Bool {
                                    return false
                                }
                            }
                            var pictureInPictureController: AVPictureInPictureController? = nil
                            if #available(iOS 15.0, *) {
                                pictureInPictureController = AVPictureInPictureController(contentSource: AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: sampleBufferVideoView.sampleBufferLayer, playbackDelegate: {
                                    let delegate = PlaybackDelegateImpl()
                                    delegate.onTransitionFinished = {
                                    }
                                    return delegate
                                }()))
                                pictureInPictureController?.playerLayer.masksToBounds = false
                                pictureInPictureController?.playerLayer.cornerRadius = 10
                            } else if AVPictureInPictureController.isPictureInPictureSupported() {
                                pictureInPictureController = AVPictureInPictureController.init(playerLayer: AVPlayerLayer(player: AVPlayer()))
                            }
                            
                            pictureInPictureController?.delegate = self
                            if #available(iOS 14.2, *) {
                                pictureInPictureController?.canStartPictureInPictureAutomaticallyFromInline = true
                            }
                            if #available(iOS 14.0, *) {
                                pictureInPictureController?.requiresLinearPlayback = true
                            }
                            self.pictureInPictureController = pictureInPictureController
                        }
                        
                        videoView.setOnOrientationUpdated { [weak state] _, _ in
                            state?.updated(transition: .immediate)
                        }
                        videoView.setOnFirstFrameReceived { [weak self, weak state] _ in
                            guard let strongSelf = self else {
                                return
                            }
                            
                            strongSelf.hadVideo = true
                            
                            strongSelf.noSignalTimer?.invalidate()
                            strongSelf.noSignalTimer = nil
                            strongSelf.noSignalTimeout = false
                            strongSelf.noSignalView?.removeFromSuperview()
                            strongSelf.noSignalView = nil
                            
                            state?.updated(transition: .immediate)
                        }
                    }
                    
                    if let videoView = self.videoView, let videoBlurView = self.videoRenderingContext.makeBlurView(input: input, mainView: videoView) {
                        self.videoBlurView = videoBlurView
                        self.insertSubview(videoBlurView, belowSubview: self.blurTintView)
                        videoBlurView.alpha = 0
                        UIView.animate(withDuration: 0.3) {
                            videoBlurView.alpha = 1
                        }
                        self.videoBlurGradientMask.type = .radial
                        self.videoBlurGradientMask.colors = [UIColor(rgb: 0x000000, alpha: 0.5).cgColor, UIColor(rgb: 0xffffff, alpha: 0.0).cgColor]
                        self.videoBlurGradientMask.startPoint = CGPoint(x: 0.5, y: 0.5)
                        self.videoBlurGradientMask.endPoint = CGPoint(x: 1.0, y: 1.0)
                        
                        self.videoBlurSolidMask.backgroundColor = UIColor.black.cgColor
                        self.videoBlurGradientMask.addSublayer(videoBlurSolidMask)
                        
                    }
                }
            } else if component.isFullscreen {
                if fullScreenBackgroundPlaceholder.superview == nil {
                    insertSubview(fullScreenBackgroundPlaceholder, at: 0)
                    transition.setAlpha(view: self.fullScreenBackgroundPlaceholder, alpha: 1)
                }
                fullScreenBackgroundPlaceholder.backgroundColor = UIColor.black.withAlphaComponent(0.5)
            } else {
                transition.setAlpha(view: self.fullScreenBackgroundPlaceholder, alpha: 0, completion: { didComplete in
                    if didComplete {
                        self.fullScreenBackgroundPlaceholder.removeFromSuperview()
                    }
                })
            }
            fullScreenBackgroundPlaceholder.frame = .init(origin: .zero, size: availableSize)
            
            let videoInset: CGFloat
            if !component.isFullscreen {
                videoInset = 16
            } else {
                videoInset = 0
            }
            
            let videoSize: CGSize
            let videoCornerRadius: CGFloat = component.isFullscreen ? 0 : 10
            
            let videoFrameUpdateTransition: ComponentTransition
            if self.wasFullscreen != component.isFullscreen {
                videoFrameUpdateTransition = transition
            } else {
                videoFrameUpdateTransition = transition.withAnimation(.none)
            }
            
            if let videoView = self.videoView {
                if let peerId = component.call.peerId, videoView.bounds.size.width > 0,
                    videoView.alpha > 0,
                    self.hadVideo,
                    let snapshot = videoView.snapshotView(afterScreenUpdates: false) ?? videoView.snapshotView(afterScreenUpdates: true) {
                    lastFrame[peerId.id.description] = snapshot
                }
                
                var aspect = videoView.getAspect()
                if component.isFullscreen && self.hadVideo {
                    if aspect <= 0.01 {
                        aspect = 16.0 / 9
                    }
                } else if !self.hadVideo {
                    aspect = 16.0 / 9
                }
                
                if component.isFullscreen {
                    videoSize = CGSize(width: aspect * 100.0, height: 100.0).aspectFitted(.init(width: availableSize.width - videoInset * 2, height: availableSize.height))
                } else {
                    // Limiting by smallest side -- redundant if passing precalculated availableSize
                    let availableVideoWidth = min(availableSize.width, availableSize.height) - videoInset * 2
                    let availableVideoHeight = availableVideoWidth * 9.0 / 16
                    
                    videoSize = CGSize(width: aspect * 100.0, height: 100.0).aspectFitted(.init(width: availableVideoWidth, height: availableVideoHeight))
                }
                let blurredVideoSize = component.isFullscreen ? availableSize : videoSize.aspectFilled(availableSize)
                
                component.onVideoSizeRetrieved(videoSize)
                
                var isVideoVisible = component.isVisible
                
                if !self.wasVisible && component.isVisible {
                    videoView.layer.animateAlpha(from: 0, to: 1, duration: 0.2)
                } else if self.wasVisible && !component.isVisible {
                    videoView.layer.animateAlpha(from: 1, to: 0, duration: 0.2)
                }
                
                if let pictureInPictureController = self.pictureInPictureController {
                    if pictureInPictureController.isPictureInPictureActive {
                        isVideoVisible = true
                    }
                }
                
                videoView.updateIsEnabled(isVideoVisible)
                videoView.clipsToBounds = true
                videoView.layer.cornerRadius = videoCornerRadius
                
                self.wasFullscreen = component.isFullscreen
                let newVideoFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - videoSize.width) / 2.0), y: floor((availableSize.height - videoSize.height) / 2.0)), size: videoSize)
                
                videoFrameUpdateTransition.setFrame(view: videoView, frame: newVideoFrame, completion: nil)
                
                if let videoBlurView = self.videoBlurView {
                    videoBlurView.updateIsEnabled(component.isVisible)
                    if component.isFullscreen {
                        videoFrameUpdateTransition.setFrame(view: videoBlurView, frame: CGRect(
                            origin: CGPoint(x: floor((availableSize.width - blurredVideoSize.width) / 2.0), y: floor((availableSize.height - blurredVideoSize.height) / 2.0)),
                            size: blurredVideoSize
                        ), completion: nil)
                    } else {
                        videoFrameUpdateTransition.setFrame(view: videoBlurView, frame: videoView.frame.insetBy(dx: -70.0 * aspect, dy: -70.0))
                    }
                    
                    videoBlurView.layer.mask = videoBlurGradientMask
                    
                    if !component.isFullscreen {
                        transition.setAlpha(layer: videoBlurSolidMask, alpha: 0)
                    } else {
                        transition.setAlpha(layer: videoBlurSolidMask, alpha: 1)
                    }
                    
                    videoFrameUpdateTransition.setFrame(layer: self.videoBlurGradientMask, frame: videoBlurView.bounds)
                    videoFrameUpdateTransition.setFrame(layer: self.videoBlurSolidMask, frame: self.videoBlurGradientMask.bounds)
                }
                
                if component.call.accountContext.sharedContext.immediateExperimentalUISettings.liveStreamV2 && self.livestreamVideoView == nil {
                    let livestreamVideoView = LivestreamVideoViewV1(context: component.call.accountContext, audioSessionManager: component.call.accountContext.sharedContext.mediaManager.audioSession, call: component.call)
                    self.livestreamVideoView = livestreamVideoView
                    livestreamVideoView.layer.masksToBounds = true
                    self.addSubview(livestreamVideoView)
                    livestreamVideoView.frame = newVideoFrame
                    livestreamVideoView.layer.cornerRadius = videoCornerRadius
                    livestreamVideoView.update(size: newVideoFrame.size, transition: .immediate)
                    
                    /*var pictureInPictureController: AVPictureInPictureController? = nil
                    if #available(iOS 15.0, *) {
                        pictureInPictureController = AVPictureInPictureController(contentSource: AVPictureInPictureController.ContentSource(playerLayer: livePlayerView.playerLayer))
                        pictureInPictureController?.playerLayer.masksToBounds = false
                        pictureInPictureController?.playerLayer.cornerRadius = 10
                    } else if AVPictureInPictureController.isPictureInPictureSupported() {
                        pictureInPictureController = AVPictureInPictureController.init(playerLayer: AVPlayerLayer(player: AVPlayer()))
                    }
                    
                    pictureInPictureController?.delegate = self
                    if #available(iOS 14.2, *) {
                        pictureInPictureController?.canStartPictureInPictureAutomaticallyFromInline = true
                    }
                    if #available(iOS 14.0, *) {
                        pictureInPictureController?.requiresLinearPlayback = true
                    }
                    self.pictureInPictureController = pictureInPictureController*/
                }
                if let livestreamVideoView = self.livestreamVideoView {
                    videoFrameUpdateTransition.setFrame(view: livestreamVideoView, frame: newVideoFrame, completion: nil)
                    videoFrameUpdateTransition.setCornerRadius(layer: livestreamVideoView.layer, cornerRadius: videoCornerRadius)
                    livestreamVideoView.update(size: newVideoFrame.size, transition: transition.containedViewLayoutTransition)
                    
                    videoView.isHidden = true
                }
            } else {
                let availableVideoWidth = min(availableSize.width, availableSize.height) - videoInset * 2
                let availableVideoHeight = availableVideoWidth * 9.0 / 16
                videoSize = CGSize(width: 16 / 9 * 100.0, height: 100.0).aspectFitted(.init(width: availableVideoWidth, height: availableVideoHeight))
            }
            
            let loadingBlurViewFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - videoSize.width) / 2.0), y: floor((availableSize.height - videoSize.height) / 2.0)), size: videoSize)
            
            if self.loadingBlurView.frame == .zero {
                self.loadingBlurView.frame = loadingBlurViewFrame
            } else {
                // Using ComponentTransition.setFrame on UIVisualEffectView causes instant update of sublayers
                switch videoFrameUpdateTransition.animation {
                case let .curve(duration, curve):
                    UIView.animate(withDuration: duration, delay: 0, options: curve.containedViewLayoutTransitionCurve.viewAnimationOptions, animations: { [weak self] in
                        guard let self else {
                            return
                        }
                        self.loadingBlurView.frame = loadingBlurViewFrame
                    })
                    
                default:
                    self.loadingBlurView.frame = loadingBlurViewFrame
                }
            }
            videoFrameUpdateTransition.setCornerRadius(layer: self.loadingBlurView.layer, cornerRadius: videoCornerRadius)
            videoFrameUpdateTransition.setFrame(view: self.placeholderView, frame: loadingBlurViewFrame)
            videoFrameUpdateTransition.setCornerRadius(layer: self.placeholderView.layer, cornerRadius: videoCornerRadius)
            self.placeholderView.clipsToBounds = true
            self.placeholderView.subviews.forEach {
                videoFrameUpdateTransition.setFrame(view: $0, frame: self.placeholderView.bounds)
            }
            
            let initialShimmerBounds = self.shimmerBorderLayer.bounds
            videoFrameUpdateTransition.setFrame(layer: self.shimmerBorderLayer, frame: loadingBlurView.bounds)
            
            let borderMask = CAShapeLayer()
            let initialPath = CGPath(roundedRect: .init(x: 0, y: 0, width: initialShimmerBounds.width, height: initialShimmerBounds.height), cornerWidth: videoCornerRadius, cornerHeight: videoCornerRadius, transform: nil)
            borderMask.path = initialPath
            
            videoFrameUpdateTransition.setShapeLayerPath(layer: borderMask, path: CGPath(roundedRect: .init(x: 0, y: 0, width: shimmerBorderLayer.bounds.width, height: shimmerBorderLayer.bounds.height), cornerWidth: videoCornerRadius, cornerHeight: videoCornerRadius, transform: nil))
            
            borderMask.fillColor = UIColor.white.withAlphaComponent(0.4).cgColor
            borderMask.strokeColor = UIColor.white.withAlphaComponent(0.7).cgColor
            borderMask.lineWidth = 3
            self.shimmerBorderLayer.mask = borderMask
            self.shimmerBorderLayer.cornerRadius = videoCornerRadius
            
            if !self.hadVideo && !component.call.accountContext.sharedContext.immediateExperimentalUISettings.liveStreamV2 {
                if self.noSignalTimer == nil {
                    if #available(iOS 10.0, *) {
                        let noSignalTimer = Timer(timeInterval: 20.0, repeats: false, block: { [weak self] _ in
                            guard let strongSelf = self else {
                                return
                            }
                            strongSelf.noSignalTimeout = true
                            strongSelf.state?.updated(transition: .immediate)
                        })
                        self.noSignalTimer = noSignalTimer
                        RunLoop.main.add(noSignalTimer, forMode: .common)
                    }
                }
                
                if self.noSignalTimeout, !"".isEmpty {
                    var noSignalTransition = transition
                    let noSignalView: ComponentHostView<Empty>
                    if let current = self.noSignalView {
                        noSignalView = current
                    } else {
                        noSignalTransition = transition.withAnimation(.none)
                        noSignalView = ComponentHostView<Empty>()
                        self.noSignalView = noSignalView
                        
                        self.addSubview(noSignalView)
                        noSignalView.layer.zPosition = loadingBlurView.layer.zPosition + 1
                        
                        noSignalView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
                    }
                    
                    let presentationData = component.call.accountContext.sharedContext.currentPresentationData.with { $0 }
                    let noSignalSize = noSignalView.update(
                        transition: transition,
                        component: AnyComponent(MultilineTextComponent(
                            text: .plain(NSAttributedString(string: component.isAdmin ? presentationData.strings.LiveStream_NoSignalAdminText :  presentationData.strings.LiveStream_NoSignalUserText(component.peerTitle).string, font: Font.regular(16.0), textColor: .white, paragraphAlignment: .center)),
                            horizontalAlignment: .center,
                            maximumNumberOfLines: 0
                        )),
                        environment: {},
                        containerSize: CGSize(width: availableSize.width - 16.0 * 2.0, height: 1000.0)
                    )
                    noSignalTransition.setFrame(view: noSignalView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - noSignalSize.width) / 2.0), y: (availableSize.height - noSignalSize.height) / 2.0), size: noSignalSize), completion: nil)
                }
            }
            
            self.component = component
            
            component.activatePictureInPicture.connect { [weak self] completion in
                guard let strongSelf = self, let pictureInPictureController = strongSelf.pictureInPictureController else {
                    return
                }
                
                pictureInPictureController.startPictureInPicture()
                
                completion(Void())
            }
            
            component.deactivatePictureInPicture.connect { [weak self] _ in
                guard let strongSelf = self else {
                    return
                }
                
                strongSelf.expandFromPictureInPicture()
            }
            
            return availableSize
        }
        
        func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
            if let videoView = self.videoView, let presentation = videoView.snapshotView(afterScreenUpdates: false) {
                let presentationParent = self.window ?? self
                presentationParent.addSubview(presentation)
                presentation.frame = presentationParent.convert(videoView.frame, from: self)
                
                if let callId = self.component?.call.peerId?.id.description {
                    lastFrame[callId] = presentation
                }
                
                videoView.alpha = 0
                lastPresentation?.removeFromSuperview()
                lastPresentation = presentation
                
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
                    self.lastPresentation?.removeFromSuperview()
                    self.lastPresentation = nil
                    self.pipTrackDisplayLink?.invalidate()
                    self.pipTrackDisplayLink = nil
                }
            }
            UIView.animate(withDuration: 0.1) { [self] in
                videoBlurView?.alpha = 0
            }
            // TODO: assure player window
            UIApplication.shared.windows.first?.layer.cornerRadius = 10.0
            UIApplication.shared.windows.first?.layer.masksToBounds = true
            
            self.pipTrackDisplayLink?.invalidate()
            self.pipTrackDisplayLink = CADisplayLink(target: self, selector: #selector(observePiPWindow))
            self.pipTrackDisplayLink?.add(to: .main, forMode: .default)
        }
        
        @objc func observePiPWindow() {
            let pipViewDidBecomeVisible = (UIApplication.shared.windows.first?.layer.animationKeys()?.count ?? 0) > 0
            if pipViewDidBecomeVisible {
                lastPresentation?.removeFromSuperview()
                lastPresentation = nil
                self.pipTrackDisplayLink?.invalidate()
                self.pipTrackDisplayLink = nil
            }
        }
        
        public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
            guard let component = self.component else {
                completionHandler(false)
                return
            }
            didRequestBringBack = true
            component.bringBackControllerForPictureInPictureDeactivation {
                completionHandler(true)
            }
        }
        
        func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
            self.didRequestBringBack = false
            self.state?.updated(transition: .immediate)
        }
        
        func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
            if self.requestedExpansion {
                self.requestedExpansion = false
            } else if !didRequestBringBack {
                self.component?.pictureInPictureClosed()
            }
            didRequestBringBack = false
            // TODO: extract precise animation timing or observe window changes
            // Handle minimized case separatelly (can we detect minimized?)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
                self.videoView?.alpha = 1
            }
            UIView.animate(withDuration: 0.3) { [self] in
                self.videoBlurView?.alpha = 1
            }
        }
        
        func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
            self.videoView?.alpha = 1
            self.state?.updated(transition: .immediate)
        }
    }
    
    public func makeView() -> View {
        return View(frame: CGRect())
    }
    
    public func update(view: View, availableSize: CGSize, state: State, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
        return view.update(component: self, availableSize: availableSize, state: state, transition: transition)
    }
}

// TODO: move to appropriate place
fileprivate var lastFrame: [String: UIView] = [:]

private final class CustomIntensityVisualEffectView: UIVisualEffectView {
    private var animator: UIViewPropertyAnimator!
    
    init(effect: UIVisualEffect, intensity: CGFloat) {
        super.init(effect: nil)
        animator = UIViewPropertyAnimator(duration: 1, curve: .linear) { [weak self] in self?.effect = effect }
        animator.startAnimation()
        animator.pauseAnimation()
        animator.fractionComplete = intensity
        animator.pausesOnCompletion = true
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError()
    }
    
    deinit {
        animator.stopAnimation(true)
    }
}

private final class ProxyVideoView: UIView {
    private let call: PresentationGroupCallImpl
    private let id: Int64
    private let player: AVPlayer
    private let playerItem: AVPlayerItem
    let playerLayer: AVPlayerLayer
    
    private var contextDisposable: Disposable?
    
    private var failureObserverId: AnyObject?
    private var errorObserverId: AnyObject?
    private var rateObserver: NSKeyValueObservation?
    
    private var isActiveDisposable: Disposable?
    
    init(context: AccountContext, call: PresentationGroupCallImpl) {
        self.call = call
        
        self.id = Int64.random(in: Int64.min ... Int64.max)
        
        let assetUrl = "http://127.0.0.1:\(SharedHLSServer.shared.port)/\(call.internalId)/master.m3u8"
        Logger.shared.log("MediaStreamVideoComponent", "Initializing HLS asset at \(assetUrl)")
        #if DEBUG
        print("Initializing HLS asset at \(assetUrl)")
        #endif
        let asset = AVURLAsset(url: URL(string: assetUrl)!, options: [:])
        self.playerItem = AVPlayerItem(asset: asset)
        self.player = AVPlayer(playerItem: self.playerItem)
        self.player.allowsExternalPlayback = true
        self.playerLayer = AVPlayerLayer(player: self.player)
        
        super.init(frame: CGRect())
        
        self.failureObserverId = NotificationCenter.default.addObserver(forName: AVPlayerItem.failedToPlayToEndTimeNotification, object: playerItem, queue: .main, using: { notification in
            print("Player Error: \(notification.description)")
        })
        self.errorObserverId = NotificationCenter.default.addObserver(forName: AVPlayerItem.newErrorLogEntryNotification, object: playerItem, queue: .main, using: { notification in
            print("Player Error: \(notification.description)")
        })
        self.rateObserver = self.player.observe(\.rate, changeHandler: { [weak self] _, change in
            guard let self else {
                return
            }
            print("Player rate: \(self.player.rate)")
        })
        
        self.layer.addSublayer(self.playerLayer)
        
        self.isActiveDisposable = (context.sharedContext.applicationBindings.applicationIsActive
        |> distinctUntilChanged
        |> deliverOnMainQueue).start(next: { [weak self] isActive in
            guard let self else {
                return
            }
            if isActive {
                self.playerLayer.player = self.player
                if self.player.rate == 0.0 {
                    self.player.play()
                }
            } else {
                self.playerLayer.player = nil
            }
        })
        
        self.player.play()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    deinit {
        self.contextDisposable?.dispose()
        if let failureObserverId = self.failureObserverId {
            NotificationCenter.default.removeObserver(failureObserverId)
        }
        if let errorObserverId = self.errorObserverId {
            NotificationCenter.default.removeObserver(errorObserverId)
        }
        if let rateObserver = self.rateObserver {
            rateObserver.invalidate()
        }
        self.isActiveDisposable?.dispose()
    }
    
    func update(size: CGSize) {
        self.playerLayer.frame = CGRect(origin: CGPoint(), size: size)
    }
}

